using System;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;
using Microsoft.Practices.EnterpriseLibrary.Caching.Instrumentation;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Caching.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Caching;

namespace Sinacor.Infra.Common.Caching
{
	/// <summary>
	/// This Class represents the interface to caching. 
	/// All caching operations are performed through this class.
	/// </summary>
	public class CrossAppDomainCache : CrossAppDomainSingleton<CrossAppDomainCache>, IDisposable
	{
		const string _argumentOutOfRangeExceptionMessage = "Date/time expiration is inferior than current date/time";
		const int MAXIMUM_ELEMENTS_IN_CACHE_BEFORE_SCAVENGING = 1000;
		const int NUMBER_TO_REMOVE_WHEN_SCAVENGING = 10;
		const int EXPIRATION_POLL_FREQUENCY_IN_SECONDS = 60;

		ExpirationPollTimer _timer;
		Microsoft.Practices.EnterpriseLibrary.Caching.Cache _cache;
		BackgroundScheduler _scheduler;

		public CrossAppDomainCache()
		{
			CachingInstrumentationProvider instrumentationProvider = new CachingInstrumentationProvider();
			CacheCapacityScavengingPolicy scavengingPolicy = new CacheCapacityScavengingPolicy(MAXIMUM_ELEMENTS_IN_CACHE_BEFORE_SCAVENGING);

			IBackingStore backingStore = new Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore();

			_cache = new Microsoft.Practices.EnterpriseLibrary.Caching.Cache(backingStore, scavengingPolicy, instrumentationProvider);
			_timer = new ExpirationPollTimer();

			ExpirationTask expirationTask = new ExpirationTask(_cache, instrumentationProvider);

			ScavengerTask scavengerTask = new ScavengerTask(NUMBER_TO_REMOVE_WHEN_SCAVENGING, scavengingPolicy, _cache, instrumentationProvider);

			_scheduler = new BackgroundScheduler(expirationTask, scavengerTask, instrumentationProvider);
			
			_cache.Initialize(_scheduler);
			
			_scheduler.Start();
			
			_timer.StartPolling(new TimerCallback(_scheduler.ExpirationTimeoutExpired), EXPIRATION_POLL_FREQUENCY_IN_SECONDS * 1000);
		}

		public int Count
		{
			get
			{
				return _cache.Count;
			}
		}

		/// <summary>
		/// Retrieves or adds an Item to cache.
		/// </summary>
		/// <param name="key">Identifier for the Item.</param>
		/// <returns>Item in cache associated to identifier.</returns>
		public object this[string key]
		{
			get { return GetData(key); }
			set { Add(key, value); }
		}

		/// <summary>
		/// Verifica se existe no cache um item identificado pela chave informada
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public bool Contains(string key)
		{
			return _cache.Contains(key);
		}

		/// <summary>
		/// Adds a new CacheItem to cache. If another another item exists with the same key, that item
		/// is removed before the new item is added. If any failure occurs during this process, the
		/// cache will not contain the item being added.
		/// </summary>
		/// <param name="key">Identifier for this Item.</param>
		/// <param name="value">Value to be stored in cache. May be null.</param>
		/// <exception cref="ArgumentException">System.ArgumentNullException</exception>
		/// <exception cref="ArgumentNullException">System.ArgumentNullException</exception>
		public void Add(string key, object value)
		{
			_cache.Add(key, value);
		}

		/// <summary>
		/// Adds a new CacheItem to cache. If another another item exists with the same key, that item
		/// is removed before the new item is added. If any failure occurs during this process, the
		/// cache will not contain the item being added.
		/// </summary>
		/// <param name="key">Identifier for this Item.</param>
		/// <param name="value">Value to be stored in cache. May be null.</param>
		/// <param name="slidingTimeOut">Timeout policie to be applied to this item.</param>
		/// <exception cref="ArgumentException">System.ArgumentNullException</exception>
		/// <exception cref="ArgumentNullException">System.ArgumentNullException</exception>
		public void Add(string key, object value, TimeSpan slidingTimeOut)
		{
			_cache.Add(key, value, CacheItemPriority.Normal, null, new SlidingTime(slidingTimeOut));
		}

		/// <summary>
		/// Adds a new CacheItem to cache. If another another item exists with the same key, that item
		/// is removed before the new item is added. If any failure occurs during this process, the
		/// cache will not contain the item being added.
		/// </summary>
		/// <param name="key">Identifier for this Item.</param>
		/// <param name="value">Value to be stored in cache. May be null.</param>
		/// <param name="absoluteTimeOut">Expiration date to be applied to this item.</param>
		/// <exception cref="ArgumentException">System.ArgumentNullException</exception>
		/// <exception cref="ArgumentNullException">System.ArgumentNullException</exception>
		/// <exception cref="ArgumentOutOfRangeException">System.ArgumentOutOfRangeException</exception>
		public void Add(string key, object value, DateTime absoluteTimeOut)
		{
			if (absoluteTimeOut <= DateTime.Now)
				throw new ArgumentOutOfRangeException("absoluteTimeOut", _argumentOutOfRangeExceptionMessage);

			_cache.Add(key, value, CacheItemPriority.Normal, null, new AbsoluteTime(absoluteTimeOut));
		}

		/// <summary>
		/// Remove um item do cache
		/// </summary>
		/// <param name="key"></param>
		public void Remove(string key)
		{
			_cache.Remove(key);
		}

		/// <summary>
		/// Retrieves an Item to cache.
		/// </summary>
		/// <param name="key">Identifier for the Item.</param>
		/// <returns>Item retrieved from cache.</returns>
		public object GetData(string key)
		{
			return _cache.GetData(key);
		}		

		/// <summary>
		/// Flush the itesm inside de cache
		/// </summary>
		public void Flush()
		{
			_cache.Flush();
		}

		/// <summary>
		/// Disposes the cache class
		/// </summary>
		public void Dispose()
		{
			GC.SuppressFinalize(this);
			if (_scheduler != null)
			{
				_scheduler.Stop();
				_scheduler = null;
			}
			if (_timer != null)
			{
				_timer.StopPolling();
				_timer = null;
			}
			if (_cache != null)
			{
				_cache.Dispose();
				_cache = null;
			}
		}
	}
}